/*
 *  compiler/compemu_fpp.cpp - Dynamic translation of FPU instructions
 *
 *  Original 68040 JIT compiler for UAE, copyright 2000-2002 Bernd Meyer
 *
 *  Adaptation for Basilisk II and improvements, copyright 2000-2005
 *    Gwenole Beauchesne
 *
 *  Basilisk II (C) 1997-2008 Christian Bauer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * UAE - The Un*x Amiga Emulator
 *
 * MC68881 emulation
 *
 * Copyright 1996 Herman ten Brugge
 * Adapted for JIT compilation (c) Bernd Meyer, 2000
 */

#include "sysdeps.h"

#include <math.h>
#include <stdio.h>

#include "memory.h"
#include "readcpu.h"
#include "newcpu.h"
#include "main.h"
#include "compiler/compemu.h"
#include "fpu/fpu.h"
#include "fpu/flags.h"
#include "fpu/exceptions.h"
#include "fpu/rounding.h"

#define DEBUG 0
#include "debug.h"

// gb-- WARNING: get_fpcr() and set_fpcr() support is experimental
#define HANDLE_FPCR 0

// - IEEE-based fpu core must be used
#if defined (FPU_IEEE)
    #define CAN_HANDLE_FPCR
#endif

// - Generic rounding mode and precision modes are supported if set together
#if defined (FPU_USE_GENERIC_ROUNDING_MODE) && defined (FPU_USE_GENERIC_ROUNDING_PRECISION)
    #define CAN_HANDLE_FPCR
#endif

// - X86 rounding mode and precision modes are *not* supported but might work (?!)
#if defined (FPU_USE_X86_ROUNDING_MODE) && defined (FPU_USE_X86_ROUNDING_PRECISION)
    #define CAN_HANDLE_FPCR
#endif

#if HANDLE_FPCR && !defined (CAN_HANDLE_FPCR)
    #warning "Can't handle FPCR, will FAIL(1) at runtime"
    #undef HANDLE_FPCR
    #define HANDLE_FPCR 0
#endif

#define STATIC_INLINE static inline
#define MAKE_FPSR(r) do { fmov_rr(FP_RESULT, r); } while (0)

#define delay nop(); nop()
#define delay2 nop(); nop()

#define UNKNOWN_EXTRA 0xFFFFFFFF
static void fpuop_illg(uae_u32 opcode, uae_u32 extra)
{
    /*
        if (extra == UNKNOWN_EXTRA)
            printf("FPU opcode %x, extra UNKNOWN_EXTRA\n",opcode & 0xFFFF);
        else
            printf("FPU opcode %x, extra %x\n",opcode & 0xFFFF,extra & 0xFFFF);
     */
    op_illg(opcode);
}

static uae_s32 temp_fp[4];  /* To convert between FP/integer */

/* return register number, or -1 for failure */
STATIC_INLINE int get_fp_value(uae_u32 opcode, uae_u16 extra)
{
    uaecptr tmppc;
    uae_u16 tmp;
    int size;
    int mode;
    int reg;
    double* src;
    uae_u32 ad = 0;
    static int sz1[8] = { 4, 4, 12, 12, 2, 8, 1, 0 };
    static int sz2[8] = { 4, 4, 12, 12, 2, 8, 2, 0 };

    if ((extra & 0x4000) == 0)
    {
        return ((extra >> 10) & 7);
    }

    mode = (opcode >> 3) & 7;
    reg = opcode & 7;
    size = (extra >> 10) & 7;
    switch (mode)
    {
        case 0:
            switch (size)
            {
                case 6:
                    sign_extend_8_rr(S1, reg);
                    mov_l_mr((uintptr)temp_fp, S1);
                    delay2;
                    fmovi_rm(FS1, (uintptr)temp_fp);
                    return FS1;
                case 4:
                    sign_extend_16_rr(S1, reg);
                    mov_l_mr((uintptr)temp_fp, S1);
                    delay2;
                    fmovi_rm(FS1, (uintptr)temp_fp);
                    return FS1;
                case 0:
                    mov_l_mr((uintptr)temp_fp, reg);
                    delay2;
                    fmovi_rm(FS1, (uintptr)temp_fp);
                    return FS1;
                case 1:
                    mov_l_mr((uintptr)temp_fp, reg);
                    delay2;
                    fmovs_rm(FS1, (uintptr)temp_fp);
                    return FS1;
                default:
                    return -1;
            }
            return -1; /* Should be unreachable */
        case 1:
            return -1; /* Genuine invalid instruction */
        default:
            break;
    }
    /* OK, we *will* have to load something from an address. Let's make
       sure we know how to handle that, or quit early --- i.e. *before*
       we do any postincrement/predecrement that we may regret */

    switch (size)
    {
        case 3:
            return -1;
        case 0:
        case 1:
        case 2:
        case 4:
        case 5:
        case 6:
            break;
        default:
            return -1;
    }

    switch (mode)
    {
        case 2:
            ad = S1; /* We will change it, anyway ;-) */
            mov_l_rr(ad, reg + 8);
            break;
        case 3:
            ad = S1;
            mov_l_rr(ad, reg + 8);
            lea_l_brr(reg + 8, reg + 8, (reg == 7 ? sz2[size] : sz1[size]));
            break;
        case 4:
            ad = S1;

            lea_l_brr(reg + 8, reg + 8, -(reg == 7 ? sz2[size] : sz1[size]));
            mov_l_rr(ad, reg + 8);
            break;
        case 5:
        {
            uae_u32 off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
            ad = S1;
            mov_l_rr(ad, reg + 8);
            lea_l_brr(ad, ad, off);
            break;
        }
        case 6:
        {
            uae_u32 dp = comp_get_iword((m68k_pc_offset += 2) - 2);
            ad = S1;
            calc_disp_ea_020(reg + 8, dp, ad, S2);
            break;
        }
        case 7:
            switch (reg)
            {
                case 0:
                {
                    uae_u32 off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
                    ad = S1;
                    mov_l_ri(ad, off);
                    break;
                }
                case 1:
                {
                    uae_u32 off = comp_get_ilong((m68k_pc_offset += 4) - 4);
                    ad = S1;
                    mov_l_ri(ad, off);
                    break;
                }
                case 2:
                {
                    uae_u32 address = start_pc + ((char*)comp_pc_p - (char*)start_pc_p) +
                                      m68k_pc_offset;
                    uae_s32 PC16off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2)
                                                                       - 2);
                    ad = S1;
                    mov_l_ri(ad, address + PC16off);
                    break;
                }
                case 3:
                    return -1;
                    tmppc = m68k_getpc();
                    tmp = next_iword();
                    ad = get_disp_ea_020(tmppc, tmp);
                    break;
                case 4:
                {
                    uae_u32 address = start_pc + ((char*)comp_pc_p - (char*)start_pc_p) + m68k_pc_offset;
                    ad = S1;
                    // Immediate addressing mode && Operation Length == Byte ->
                    // Use the low-order byte of the extension word.
                    if (size == 6) address++;
                    mov_l_ri(ad, address);
                    m68k_pc_offset += sz2[size];
                    break;
                }
                default:
                    return -1;
            }
    }

    switch (size)
    {
        case 0:
            readlong(ad, S2, S3);
            mov_l_mr((uintptr)temp_fp, S2);
            delay2;
            fmovi_rm(FS1, (uintptr)temp_fp);
            break;
        case 1:
            readlong(ad, S2, S3);
            mov_l_mr((uintptr)temp_fp, S2);
            delay2;
            fmovs_rm(FS1, (uintptr)temp_fp);
            break;
        case 2:
            readword(ad, S2, S3);
            mov_w_mr(((uintptr)temp_fp) + 8, S2);
            add_l_ri(ad, 4);
            readlong(ad, S2, S3);
            mov_l_mr((uintptr)(temp_fp) + 4, S2);
            add_l_ri(ad, 4);
            readlong(ad, S2, S3);
            mov_l_mr((uintptr)(temp_fp), S2);
            delay2;
            fmov_ext_rm(FS1, (uintptr)(temp_fp));
            break;
        case 3:
            return -1; /* Some silly "packed" stuff */
        case 4:
            readword(ad, S2, S3);
            sign_extend_16_rr(S2, S2);
            mov_l_mr((uintptr)temp_fp, S2);
            delay2;
            fmovi_rm(FS1, (uintptr)temp_fp);
            break;
        case 5:
            readlong(ad, S2, S3);
            mov_l_mr(((uintptr)temp_fp) + 4, S2);
            add_l_ri(ad, 4);
            readlong(ad, S2, S3);
            mov_l_mr((uintptr)(temp_fp), S2);
            delay2;
            fmov_rm(FS1, (uintptr)(temp_fp));
            break;
        case 6:
            readbyte(ad, S2, S3);
            sign_extend_8_rr(S2, S2);
            mov_l_mr((uintptr)temp_fp, S2);
            delay2;
            fmovi_rm(FS1, (uintptr)temp_fp);
            break;
        default:
            return -1;
    }
    return FS1;
}

/* return of -1 means failure, >=0 means OK */
STATIC_INLINE int put_fp_value(int val, uae_u32 opcode, uae_u16 extra)
{
    uae_u16 tmp;
    uaecptr tmppc;
    int size;
    int mode;
    int reg;
    uae_u32 ad;
    static int sz1[8] = { 4, 4, 12, 12, 2, 8, 1, 0 };
    static int sz2[8] = { 4, 4, 12, 12, 2, 8, 2, 0 };

    if ((extra & 0x4000) == 0)
    {
        const int dest_reg = (extra >> 10) & 7;
        fmov_rr(dest_reg, val);
        // gb-- status register is affected
        MAKE_FPSR(dest_reg);
        return 0;
    }

    mode = (opcode >> 3) & 7;
    reg = opcode & 7;
    size = (extra >> 10) & 7;
    ad = (uae_u32) - 1;
    switch (mode)
    {
        case 0:
            switch (size)
            {
                case 6:
                    fmovi_mr((uintptr)temp_fp, val);
                    delay;
                    mov_b_rm(reg, (uintptr)temp_fp);
                    return 0;
                case 4:
                    fmovi_mr((uintptr)temp_fp, val);
                    delay;
                    mov_w_rm(reg, (uintptr)temp_fp);
                    return 0;
                case 0:
                    fmovi_mr((uintptr)temp_fp, val);
                    delay;
                    mov_l_rm(reg, (uintptr)temp_fp);
                    return 0;
                case 1:
                    fmovs_mr((uintptr)temp_fp, val);
                    delay;
                    mov_l_rm(reg, (uintptr)temp_fp);
                    return 0;
                default:
                    return -1;
            }
        case 1:
            return -1; /* genuine invalid instruction */
        default: break;
    }

    /* Let's make sure we get out *before* doing something silly if
       we can't handle the size */
    switch (size)
    {
        case 0:
        case 4:
        case 5:
        case 6:
        case 2:
        case 1:
            break;
        case 3:
        default:
            return -1;
    }

    switch (mode)
    {
        case 2:
            ad = S1;
            mov_l_rr(ad, reg + 8);
            break;
        case 3:
            ad = S1;
            mov_l_rr(ad, reg + 8);
            lea_l_brr(reg + 8, reg + 8, (reg == 7 ? sz2[size] : sz1[size]));
            break;
        case 4:
            ad = S1;
            lea_l_brr(reg + 8, reg + 8, -(reg == 7 ? sz2[size] : sz1[size]));
            mov_l_rr(ad, reg + 8);
            break;
        case 5:
        {
            uae_u32 off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
            ad = S1;
            mov_l_rr(ad, reg + 8);
            add_l_ri(ad, off);
            break;
        }
        case 6:
        {
            uae_u32 dp = comp_get_iword((m68k_pc_offset += 2) - 2);
            ad = S1;
            calc_disp_ea_020(reg + 8, dp, ad, S2);
            break;
        }
        case 7:
            switch (reg)
            {
                case 0:
                {
                    uae_u32 off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
                    ad = S1;
                    mov_l_ri(ad, off);
                    break;
                }
                case 1:
                {
                    uae_u32 off = comp_get_ilong((m68k_pc_offset += 4) - 4);
                    ad = S1;
                    mov_l_ri(ad, off);
                    break;
                }
                case 2:
                {
                    uae_u32 address = start_pc + ((char*)comp_pc_p - (char*)start_pc_p) +
                                      m68k_pc_offset;
                    uae_s32 PC16off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
                    ad = S1;
                    mov_l_ri(ad, address + PC16off);
                    break;
                }
                case 3:
                    return -1;
                    tmppc = m68k_getpc();
                    tmp = next_iword();
                    ad = get_disp_ea_020(tmppc, tmp);
                    break;
                case 4:
                {
                    uae_u32 address = start_pc + ((char*)comp_pc_p - (char*)start_pc_p) +
                                      m68k_pc_offset;
                    ad = S1;
                    mov_l_ri(ad, address);
                    m68k_pc_offset += sz2[size];
                    break;
                }
                default:
                    return -1;
            }
    }
    switch (size)
    {
        case 0:
            fmovi_mr((uintptr)temp_fp, val);
            delay;
            mov_l_rm(S2, (uintptr)temp_fp);
            writelong_clobber(ad, S2, S3);
            break;
        case 1:
            fmovs_mr((uintptr)temp_fp, val);
            delay;
            mov_l_rm(S2, (uintptr)temp_fp);
            writelong_clobber(ad, S2, S3);
            break;
        case 2:
            fmov_ext_mr((uintptr)temp_fp, val);
            delay;
            mov_w_rm(S2, (uintptr)temp_fp + 8);
            writeword_clobber(ad, S2, S3);
            add_l_ri(ad, 4);
            mov_l_rm(S2, (uintptr)temp_fp + 4);
            writelong_clobber(ad, S2, S3);
            add_l_ri(ad, 4);
            mov_l_rm(S2, (uintptr)temp_fp);
            writelong_clobber(ad, S2, S3);
            break;
        case 3: return -1; /* Packed */

        case 4:
            fmovi_mr((uintptr)temp_fp, val);
            delay;
            mov_l_rm(S2, (uintptr)temp_fp);
            writeword_clobber(ad, S2, S3);
            break;
        case 5:
            fmov_mr((uintptr)temp_fp, val);
            delay;
            mov_l_rm(S2, (uintptr)temp_fp + 4);
            writelong_clobber(ad, S2, S3);
            add_l_ri(ad, 4);
            mov_l_rm(S2, (uintptr)temp_fp);
            writelong_clobber(ad, S2, S3);
            break;
        case 6:
            fmovi_mr((uintptr)temp_fp, val);
            delay;
            mov_l_rm(S2, (uintptr)temp_fp);
            writebyte(ad, S2, S3);
            break;
        default:
            return -1;
    }
    return 0;
}

/* return -1 for failure, or register number for success */
STATIC_INLINE int get_fp_ad(uae_u32 opcode, uae_u32* ad)
{
    uae_u16 tmp;
    uaecptr tmppc;
    int mode;
    int reg;
    uae_s32 off;

    mode = (opcode >> 3) & 7;
    reg = opcode & 7;
    switch (mode)
    {
        case 0:
        case 1:
            return -1;
        case 2:
        case 3:
        case 4:
            mov_l_rr(S1, 8 + reg);
            return S1;
            *ad = m68k_areg(regs, reg);
            break;
        case 5:
            off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);

            mov_l_rr(S1, 8 + reg);
            add_l_ri(S1, off);
            return S1;
        case 6:
            return -1;
            break;
        case 7:
            switch (reg)
            {
                case 0:
                    off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
                    mov_l_ri(S1, off);
                    return S1;
                case 1:
                    off = comp_get_ilong((m68k_pc_offset += 4) - 4);
                    mov_l_ri(S1, off);
                    return S1;
                case 2:
                    return -1;
                    //	    *ad = m68k_getpc ();
                    //	    *ad += (uae_s32) (uae_s16) next_iword ();
                    off = start_pc + ((char*)comp_pc_p - (char*)start_pc_p) + m68k_pc_offset;
                    off += (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
                    mov_l_ri(S1, off);
                    return S1;
                case 3:
                    return -1;
                    tmppc = m68k_getpc();
                    tmp = next_iword();
                    *ad = get_disp_ea_020(tmppc, tmp);
                    break;
                default:
                    return -1;
            }
    }
    abort();
}

void comp_fdbcc_opp(uae_u32 opcode, uae_u16 extra)
{
    FAIL(1);
    return;
}

void comp_fscc_opp(uae_u32 opcode, uae_u16 extra)
{
    uae_u32 ad;
    int cc;
    int reg;

    #if DEBUG_FPP
    printf("fscc_opp at %08lx\n", m68k_getpc());
    fflush(stdout);
    #endif


    if (extra & 0x20)    /* only cc from 00 to 1f are defined */
    {
        FAIL(1);
        return;
    }
    if ((opcode & 0x38) != 0)   /* We can only do to integer register */
    {
        FAIL(1);
        return;
    }

    fflags_into_flags(S2);
    reg = (opcode & 7);

    mov_l_ri(S1, 255);
    mov_l_ri(S4, 0);
    switch (extra & 0x0f)    /* according to fpp.c, the 0x10 bit is ignored
                              */
    {
        case 0: break; /* set never */
        case 1: mov_l_rr(S2, S4);
            cmov_l_rr(S4, S1, 4);
            cmov_l_rr(S4, S2, 10); break;
        case 2: cmov_l_rr(S4, S1, 7); break;
        case 3: cmov_l_rr(S4, S1, 3); break;
        case 4: mov_l_rr(S2, S4);
            cmov_l_rr(S4, S1, 2);
            cmov_l_rr(S4, S2, 10); break;
        case 5: mov_l_rr(S2, S4);
            cmov_l_rr(S4, S1, 6);
            cmov_l_rr(S4, S2, 10); break;
        case 6: cmov_l_rr(S4, S1, 5); break;
        case 7: cmov_l_rr(S4, S1, 11); break;
        case 8: cmov_l_rr(S4, S1, 10); break;
        case 9: cmov_l_rr(S4, S1, 4); break;
        case 10: cmov_l_rr(S4, S1, 10); cmov_l_rr(S4, S1, 7); break;
        case 11: cmov_l_rr(S4, S1, 4); cmov_l_rr(S4, S1, 3); break;
        case 12: cmov_l_rr(S4, S1, 2); break;
        case 13: cmov_l_rr(S4, S1, 6); break;
        case 14: cmov_l_rr(S4, S1, 5); cmov_l_rr(S4, S1, 10); break;
        case 15: mov_l_rr(S4, S1); break;
    }

    if ((opcode & 0x38) == 0)
    {
        mov_b_rr(reg, S4);
    }
    else
    {
        abort();
        if (get_fp_ad(opcode, &ad) == 0)
        {
            m68k_setpc(m68k_getpc() - 4);
            fpuop_illg(opcode, extra);
        }
        else
            put_byte(ad, cc ? 0xff : 0x00);
    }
}

void comp_ftrapcc_opp(uae_u32 opcode, uaecptr oldpc)
{
    int cc;

    FAIL(1);
    return;
}

void comp_fbcc_opp(uae_u32 opcode)
{
    uae_u32 start_68k_offset = m68k_pc_offset;
    uae_u32 off;
    uae_u32 v1;
    uae_u32 v2;
    uae_u32 nh;
    int cc;

    // comp_pc_p is expected to be bound to 32-bit addresses
    assert((uintptr)comp_pc_p <= 0xffffffffUL);

    if (opcode & 0x20)    /* only cc from 00 to 1f are defined */
    {
        FAIL(1);
        return;
    }
    if ((opcode & 0x40) == 0)
    {
        off = (uae_s32)(uae_s16)comp_get_iword((m68k_pc_offset += 2) - 2);
    }
    else
    {
        off = comp_get_ilong((m68k_pc_offset += 4) - 4);
    }
    mov_l_ri(S1, (uintptr)
             (comp_pc_p + off - (m68k_pc_offset - start_68k_offset)));
    mov_l_ri(PC_P, (uintptr)comp_pc_p);

    /* Now they are both constant. Might as well fold in m68k_pc_offset */
    add_l_ri(S1, m68k_pc_offset);
    add_l_ri(PC_P, m68k_pc_offset);
    m68k_pc_offset = 0;

    /* according to fpp.c, the 0x10 bit is ignored
       (it handles exception handling, which we don't
       do, anyway ;-) */
    cc = opcode & 0x0f;
    v1 = get_const(PC_P);
    v2 = get_const(S1);
    fflags_into_flags(S2);

    switch (cc)
    {
        case 0: break; /* jump never */
        case 1:
            mov_l_rr(S2, PC_P);
            cmov_l_rr(PC_P, S1, 4);
            cmov_l_rr(PC_P, S2, 10); break;
        case 2: register_branch(v1, v2, 7); break;
        case 3: register_branch(v1, v2, 3); break;
        case 4:
            mov_l_rr(S2, PC_P);
            cmov_l_rr(PC_P, S1, 2);
            cmov_l_rr(PC_P, S2, 10); break;
        case 5:
            mov_l_rr(S2, PC_P);
            cmov_l_rr(PC_P, S1, 6);
            cmov_l_rr(PC_P, S2, 10); break;
        case 6: register_branch(v1, v2, 5); break;
        case 7: register_branch(v1, v2, 11); break;
        case 8: register_branch(v1, v2, 10); break;
        case 9: register_branch(v1, v2, 4); break;
        case 10:
            cmov_l_rr(PC_P, S1, 10);
            cmov_l_rr(PC_P, S1, 7); break;
        case 11:
            cmov_l_rr(PC_P, S1, 4);
            cmov_l_rr(PC_P, S1, 3); break;
        case 12: register_branch(v1, v2, 2); break;
        case 13: register_branch(v1, v2, 6); break;
        case 14:
            cmov_l_rr(PC_P, S1, 5);
            cmov_l_rr(PC_P, S1, 10); break;
        case 15: mov_l_rr(PC_P, S1); break;
    }
}

/* Floating point conditions
   The "NotANumber" part could be problematic; Howver, when NaN is
   encountered, the ftst instruction sets bot N and Z to 1 on the x87,
   so quite often things just fall into place. This is probably not
   accurate wrt the 68k FPU, but it is *as* accurate as this was before.
   However, some more thought should go into fixing this stuff up so
   it accurately emulates the 68k FPU.
   >=<U
   0000    0x00: 0                        ---   Never jump
   0101    0x01: Z                        ---   jump if zero (x86: 4)
   1000    0x02: !(NotANumber || Z || N)  --- Neither Z nor N set (x86: 7)
   1101    0x03: Z || !(NotANumber || N); --- Z or !N (x86: 4 and 3)
   0010    0x04: N && !(NotANumber || Z); --- N and !Z (x86: hard!)
   0111    0x05: Z || (N && !NotANumber); --- Z or N (x86: 6)
   1010    0x06: !(NotANumber || Z);      --- not Z (x86: 5)
   1110    0x07: !NotANumber;             --- not NaN (x86: 11, not parity)
   0001    0x08: NotANumber;              --- NaN (x86: 10)
   0101    0x09: NotANumber || Z;         --- Z (x86: 4)
   1001    0x0a: NotANumber || !(N || Z); --- NaN or neither N nor Z (x86: 10 and 7)
   1101    0x0b: NotANumber || Z || !N;   --- Z or !N (x86: 4 and 3)
   0011    0x0c: NotANumber || (N && !Z); --- N (x86: 2)
   0111    0x0d: NotANumber || Z || N;    --- Z or N (x86: 6)
   1010    0x0e: !Z;                      --- not Z (x86: 5)
   1111    0x0f: 1;                       --- always

   This is not how the 68k handles things, though --- it sets Z to 0 and N
   to the NaN's sign.... ('o' and 'i' denote differences from the above
   table)

   >=<U
   0000    0x00: 0                        ---   Never jump
   010o    0x01: Z                        ---   jump if zero (x86: 4, not 10)
   1000    0x02: !(NotANumber || Z || N)  --- Neither Z nor N set (x86: 7)
   110o    0x03: Z || !(NotANumber || N); --- Z or !N (x86: 3)
   0010    0x04: N && !(NotANumber || Z); --- N and !Z (x86: 2, not 10)
   011o    0x05: Z || (N && !NotANumber); --- Z or N (x86: 6, not 10)
   1010    0x06: !(NotANumber || Z);      --- not Z (x86: 5)
   1110    0x07: !NotANumber;             --- not NaN (x86: 11, not parity)
   0001    0x08: NotANumber;              --- NaN (x86: 10)
   0101    0x09: NotANumber || Z;         --- Z (x86: 4)
   1001    0x0a: NotANumber || !(N || Z); --- NaN or neither N nor Z (x86: 10 and 7)
   1101    0x0b: NotANumber || Z || !N;   --- Z or !N (x86: 4 and 3)
   0011    0x0c: NotANumber || (N && !Z); --- N (x86: 2)
   0111    0x0d: NotANumber || Z || N;    --- Z or N (x86: 6)
   101i    0x0e: !Z;                      --- not Z (x86: 5 and 10)
   1111    0x0f: 1;                       --- always

   Of course, this *still* doesn't mean that the x86 and 68k conditions are
   equivalent --- the handling of infinities is different, for one thing.
   On the 68k, +infinity minus +infinity is NotANumber (as it should be). On
   the x86, it is +infinity, and some exception is raised (which I suspect
   is promptly ignored) STUPID!
   The more I learn about their CPUs, the more I detest Intel....

   You can see this in action if you have "Benoit" (see Aminet) and
   set the exponent to 16. Wait for a long time, and marvel at the extra black
   areas outside the center one. That's where Benoit expects NaN, and the x86
   gives +infinity. [Ooops --- that must have been some kind of bug in my code.
   it no longer happens, and the resulting graphic looks much better, too]

   x86 conditions
   0011    : 2
   1100    : 3
   0101    : 4
   1010    : 5
   0111    : 6
   1000    : 7
   0001    : 10
   1110    : 11
 */
void comp_fsave_opp(uae_u32 opcode)
{
    uae_u32 ad;
    int incr = (opcode & 0x38) == 0x20 ? -1 : 1;
    int i;

    FAIL(1);
    return;

    #if DEBUG_FPP
    printf("fsave_opp at %08lx\n", m68k_getpc());
    fflush(stdout);
    #endif
    if (get_fp_ad(opcode, &ad) == 0)
    {
        m68k_setpc(m68k_getpc() - 2);
        fpuop_illg(opcode, UNKNOWN_EXTRA);
        return;
    }

    if (CPUType == 4)
    {
        /* 4 byte 68040 IDLE frame.  */
        if (incr < 0)
        {
            ad -= 4;
            put_long(ad, 0x41000000);
        }
        else
        {
            put_long(ad, 0x41000000);
            ad += 4;
        }
    }
    else
    {
        if (incr < 0)
        {
            ad -= 4;
            put_long(ad, 0x70000000);
            for (i = 0; i < 5; i++)
            {
                ad -= 4;
                put_long(ad, 0x00000000);
            }
            ad -= 4;
            put_long(ad, 0x1f180000);
        }
        else
        {
            put_long(ad, 0x1f180000);
            ad += 4;
            for (i = 0; i < 5; i++)
            {
                put_long(ad, 0x00000000);
                ad += 4;
            }
            put_long(ad, 0x70000000);
            ad += 4;
        }
    }
    if ((opcode & 0x38) == 0x18)
        m68k_areg(regs, opcode & 7) = ad;
    if ((opcode & 0x38) == 0x20)
        m68k_areg(regs, opcode & 7) = ad;
}

void comp_frestore_opp(uae_u32 opcode)
{
    uae_u32 ad;
    uae_u32 d;
    int incr = (opcode & 0x38) == 0x20 ? -1 : 1;

    FAIL(1);
    return;

    #if DEBUG_FPP
    printf("frestore_opp at %08lx\n", m68k_getpc());
    fflush(stdout);
    #endif
    if (get_fp_ad(opcode, &ad) == 0)
    {
        m68k_setpc(m68k_getpc() - 2);
        fpuop_illg(opcode, UNKNOWN_EXTRA);
        return;
    }
    if (CPUType == 4)
    {
        /* 68040 */
        if (incr < 0)
        {
            /* @@@ This may be wrong.  */
            ad -= 4;
            d = get_long(ad);
            if ((d & 0xff000000) != 0) /* Not a NULL frame? */
            {
                if ((d & 0x00ff0000) == 0) /* IDLE */
                {
                }
                else if ((d & 0x00ff0000) == 0x00300000) /* UNIMP */
                {
                    ad -= 44;
                }
                else if ((d & 0x00ff0000) == 0x00600000) /* BUSY */
                {
                    ad -= 92;
                }
            }
        }
        else
        {
            d = get_long(ad);
            ad += 4;
            if ((d & 0xff000000) != 0) /* Not a NULL frame? */
            {
                if ((d & 0x00ff0000) == 0) /* IDLE */
                {
                }
                else if ((d & 0x00ff0000) == 0x00300000) /* UNIMP */
                {
                    ad += 44;
                }
                else if ((d & 0x00ff0000) == 0x00600000) /* BUSY */
                {
                    ad += 92;
                }
            }
        }
    }
    else
    {
        if (incr < 0)
        {
            ad -= 4;
            d = get_long(ad);
            if ((d & 0xff000000) != 0)
            {
                if ((d & 0x00ff0000) == 0x00180000)
                    ad -= 6 * 4;
                else if ((d & 0x00ff0000) == 0x00380000)
                    ad -= 14 * 4;
                else if ((d & 0x00ff0000) == 0x00b40000)
                    ad -= 45 * 4;
            }
        }
        else
        {
            d = get_long(ad);
            ad += 4;
            if ((d & 0xff000000) != 0)
            {
                if ((d & 0x00ff0000) == 0x00180000)
                    ad += 6 * 4;
                else if ((d & 0x00ff0000) == 0x00380000)
                    ad += 14 * 4;
                else if ((d & 0x00ff0000) == 0x00b40000)
                    ad += 45 * 4;
            }
        }
    }
    if ((opcode & 0x38) == 0x18)
        m68k_areg(regs, opcode & 7) = ad;
    if ((opcode & 0x38) == 0x20)
        m68k_areg(regs, opcode & 7) = ad;
}

#if USE_LONG_DOUBLE
static const fpu_register const_e = 2.7182818284590452353602874713526625L;
static const fpu_register const_log10_e = 0.4342944819032518276511289189166051L;
static const fpu_register const_loge_10 = 2.3025850929940456840179914546843642L;
#else
static const fpu_register const_e = 2.7182818284590452354;
static const fpu_register const_log10_e = 0.43429448190325182765;
static const fpu_register const_loge_10 = 2.30258509299404568402;
#endif

static const fpu_register power10[] = {
    1e0, 1e1, 1e2, 1e4, 1e8, 1e16, 1e32, 1e64, 1e128, 1e256
    #if USE_LONG_DOUBLE
    , 1e512, 1e1024, 1e2048, 1e4096
    #endif
};

/* 128 words, indexed through the low byte of the 68k fpu control word */
static uae_u16 x86_fpucw[] = {
    0x137f, 0x137f, 0x137f, 0x137f, 0x137f, 0x137f, 0x137f, 0x137f, /* p0r0 */
    0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, /* p0r1 */
    0x177f, 0x177f, 0x177f, 0x177f, 0x177f, 0x177f, 0x177f, 0x177f, /* p0r2 */
    0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, /* p0r3 */

    0x107f, 0x107f, 0x107f, 0x107f, 0x107f, 0x107f, 0x107f, 0x107f, /* p1r0 */
    0x1c7f, 0x1c7f, 0x1c7f, 0x1c7f, 0x1c7f, 0x1c7f, 0x1c7f, 0x1c7f, /* p1r1 */
    0x147f, 0x147f, 0x147f, 0x147f, 0x147f, 0x147f, 0x147f, 0x147f, /* p1r2 */
    0x187f, 0x187f, 0x187f, 0x187f, 0x187f, 0x187f, 0x187f, 0x187f, /* p1r3 */

    0x127f, 0x127f, 0x127f, 0x127f, 0x127f, 0x127f, 0x127f, 0x127f, /* p2r0 */
    0x1e7f, 0x1e7f, 0x1e7f, 0x1e7f, 0x1e7f, 0x1e7f, 0x1e7f, 0x1e7f, /* p2r1 */
    0x167f, 0x167f, 0x167f, 0x167f, 0x167f, 0x167f, 0x167f, 0x167f, /* p2r2 */
    0x1a7f, 0x1a7f, 0x1a7f, 0x1a7f, 0x1a7f, 0x1a7f, 0x1a7f, 0x1a7f, /* p2r3 */

    0x137f, 0x137f, 0x137f, 0x137f, 0x137f, 0x137f, 0x137f, 0x137f, /* p3r0 */
    0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, 0x1f7f, /* p3r1 */
    0x177f, 0x177f, 0x177f, 0x177f, 0x177f, 0x177f, 0x177f, 0x177f, /* p3r2 */
    0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f, 0x1b7f  /* p3r3 */
};

void comp_fpp_opp(uae_u32 opcode, uae_u16 extra)
{
    int reg;
    int src;

    switch ((extra >> 13) & 0x7)
    {
        case 3: /* 2nd most common */
            if (put_fp_value((extra >> 7) & 7, opcode, extra) < 0)
            {
                FAIL(1);
                return;

            }
            return;
        case 6:
        case 7:
        {
            uae_u32 ad, list = 0;
            int incr = 0;
            if (extra & 0x2000)
            {
                uae_u32 ad;

                /* FMOVEM FPP->memory */
                switch ((extra >> 11) & 3) /* Get out early if failure */
                {
                    case 0:
                    case 2:
                        break;
                    case 1:
                    case 3:
                    default:
                        FAIL(1); return;
                }
                ad = get_fp_ad(opcode, &ad);
                if (ad < 0)
                {
                    abort();
                    m68k_setpc(m68k_getpc() - 4);
                    fpuop_illg(opcode, extra);
                    return;
                }
                switch ((extra >> 11) & 3)
                {
                    case 0: /* static pred */
                        list = extra & 0xff;
                        incr = -1;
                        break;
                    case 2: /* static postinc */
                        list = extra & 0xff;
                        incr = 1;
                        break;
                    case 1: /* dynamic pred */
                    case 3: /* dynamic postinc */
                        abort();
                }
                if (incr < 0) /* Predecrement */
                {
                    for (reg = 7; reg >= 0; reg--)
                    {
                        if (list & 0x80)
                        {
                            fmov_ext_mr((uintptr)temp_fp, reg);
                            delay;
                            sub_l_ri(ad, 4);
                            mov_l_rm(S2, (uintptr)temp_fp);
                            writelong_clobber(ad, S2, S3);
                            sub_l_ri(ad, 4);
                            mov_l_rm(S2, (uintptr)temp_fp + 4);
                            writelong_clobber(ad, S2, S3);
                            sub_l_ri(ad, 4);
                            mov_w_rm(S2, (uintptr)temp_fp + 8);
                            writeword_clobber(ad, S2, S3);
                        }
                        list <<= 1;
                    }
                }
                else /* Postincrement */
                {
                    for (reg = 0; reg < 8; reg++)
                    {
                        if (list & 0x80)
                        {
                            fmov_ext_mr((uintptr)temp_fp, reg);
                            delay;
                            mov_w_rm(S2, (uintptr)temp_fp + 8);
                            writeword_clobber(ad, S2, S3);
                            add_l_ri(ad, 4);
                            mov_l_rm(S2, (uintptr)temp_fp + 4);
                            writelong_clobber(ad, S2, S3);
                            add_l_ri(ad, 4);
                            mov_l_rm(S2, (uintptr)temp_fp);
                            writelong_clobber(ad, S2, S3);
                            add_l_ri(ad, 4);
                        }
                        list <<= 1;
                    }
                }
                if ((opcode & 0x38) == 0x18)
                    mov_l_rr((opcode & 7) + 8, ad);
                if ((opcode & 0x38) == 0x20)
                    mov_l_rr((opcode & 7) + 8, ad);
            }
            else
            {
                /* FMOVEM memory->FPP */

                uae_u32 ad;
                switch ((extra >> 11) & 3) /* Get out early if failure */
                {
                    case 0:
                    case 2:
                        break;
                    case 1:
                    case 3:
                    default:
                        FAIL(1); return;
                }
                ad = get_fp_ad(opcode, &ad);
                if (ad < 0)
                {
                    abort();
                    m68k_setpc(m68k_getpc() - 4);
                    write_log("no ad\n");
                    fpuop_illg(opcode, extra);
                    return;
                }
                switch ((extra >> 11) & 3)
                {
                    case 0: /* static pred */
                        list = extra & 0xff;
                        incr = -1;
                        break;
                    case 2: /* static postinc */
                        list = extra & 0xff;
                        incr = 1;
                        break;
                    case 1: /* dynamic pred */
                    case 3: /* dynamic postinc */
                        abort();
                }

                if (incr < 0)
                {
                    // not reached
                    for (reg = 7; reg >= 0; reg--)
                    {
                        uae_u32 wrd1, wrd2, wrd3;
                        if (list & 0x80)
                        {
                            sub_l_ri(ad, 4);
                            readlong(ad, S2, S3);
                            mov_l_mr((uintptr)(temp_fp), S2);
                            sub_l_ri(ad, 4);
                            readlong(ad, S2, S3);
                            mov_l_mr((uintptr)(temp_fp) + 4, S2);
                            sub_l_ri(ad, 4);
                            readword(ad, S2, S3);
                            mov_w_mr(((uintptr)temp_fp) + 8, S2);
                            delay2;
                            fmov_ext_rm(reg, (uintptr)(temp_fp));
                        }
                        list <<= 1;
                    }
                }
                else
                {
                    for (reg = 0; reg < 8; reg++)
                    {
                        uae_u32 wrd1, wrd2, wrd3;
                        if (list & 0x80)
                        {
                            readword(ad, S2, S3);
                            mov_w_mr(((uintptr)temp_fp) + 8, S2);
                            add_l_ri(ad, 4);
                            readlong(ad, S2, S3);
                            mov_l_mr((uintptr)(temp_fp) + 4, S2);
                            add_l_ri(ad, 4);
                            readlong(ad, S2, S3);
                            mov_l_mr((uintptr)(temp_fp), S2);
                            add_l_ri(ad, 4);
                            delay2;
                            fmov_ext_rm(reg, (uintptr)(temp_fp));
                        }
                        list <<= 1;
                    }
                }
                if ((opcode & 0x38) == 0x18)
                    mov_l_rr((opcode & 7) + 8, ad);
                if ((opcode & 0x38) == 0x20)
                    mov_l_rr((opcode & 7) + 8, ad);
            }
        }
            return;

        case 4:
        case 5: /* rare */
            if ((opcode & 0x30) == 0)
            {
                if (extra & 0x2000)
                {
                    if (extra & 0x1000)
                    {
                        #if HANDLE_FPCR
                        mov_l_rm(opcode & 15, (uintptr) & fpu.fpcr.rounding_mode);
                        or_l_rm(opcode & 15, (uintptr) & fpu.fpcr.rounding_precision);
                        #else
                        FAIL(1);
                        return;
                        #endif
                    }
                    if (extra & 0x0800)
                    {
                        FAIL(1);
                        return;
                    }
                    if (extra & 0x0400)
                    {
                        mov_l_rm(opcode & 15, (uintptr) & fpu.instruction_address);
                        return;
                    }
                }
                else
                {
                    // gb-- moved here so that we may FAIL() without generating any code
                    if (extra & 0x0800)
                    {
                        // set_fpsr(m68k_dreg (regs, opcode & 15));
                        FAIL(1);
                        return;
                    }
                    if (extra & 0x1000)
                    {
                        #if HANDLE_FPCR
                        #if defined (FPU_USE_X86_ROUNDING_MODE) && defined (FPU_USE_X86_ROUNDING_PRECISION)
                        FAIL(1);
                        return;
                        #endif
                        mov_l_rr(S1, opcode & 15);
                        mov_l_rr(S2, opcode & 15);
                        and_l_ri(S1, FPCR_ROUNDING_PRECISION);
                        and_l_ri(S2, FPCR_ROUNDING_MODE);
                        mov_l_mr((uintptr) & fpu.fpcr.rounding_precision, S1);
                        mov_l_mr((uintptr) & fpu.fpcr.rounding_mode, S2);
                        #else
                        FAIL(1);
                        return;
                        #endif
                        //		    return; gb-- FMOVEM could also operate on fpiar
                    }
                    if (extra & 0x0400)
                    {
                        mov_l_mr((uintptr) & fpu.instruction_address, opcode & 15);
                        //			return; gb-- we have to process all FMOVEM bits before returning
                    }
                    return;
                }
            }
            else if ((opcode & 0x3f) == 0x3c)
            {
                if ((extra & 0x2000) == 0)
                {
                    // gb-- moved here so that we may FAIL() without generating any code
                    if (extra & 0x0800)
                    {
                        FAIL(1);
                        return;
                    }
                    if (extra & 0x1000)
                    {
                        uae_u32 val = comp_get_ilong((m68k_pc_offset += 4) - 4);
                        #if HANDLE_FPCR
                        #if defined (FPU_USE_X86_ROUNDING_MODE) && defined (FPU_USE_X86_ROUNDING_PRECISION)
                        FAIL(1);
                        return;
                        #endif
                        //		    mov_l_mi((uintptr)&regs.fpcr,val);
                        mov_l_ri(S1, val);
                        mov_l_ri(S2, val);
                        and_l_ri(S1, FPCR_ROUNDING_PRECISION);
                        and_l_ri(S2, FPCR_ROUNDING_MODE);
                        mov_l_mr((uintptr) & fpu.fpcr.rounding_precision, S1);
                        mov_l_mr((uintptr) & fpu.fpcr.rounding_mode, S2);
                        #else
                        FAIL(1);
                        return;
                        #endif
                        //		    return; gb-- FMOVEM could also operate on fpiar
                    }
                    if (extra & 0x0400)
                    {
                        uae_u32 val = comp_get_ilong((m68k_pc_offset += 4) - 4);
                        mov_l_mi((uintptr) & fpu.instruction_address, val);
                        //		    return; gb-- we have to process all FMOVEM bits before returning
                    }
                    return;
                }
                FAIL(1);
                return;
            }
            else if (extra & 0x2000)
            {
                FAIL(1);
                return;
            }
            else
            {
                FAIL(1);
                return;
            }
            FAIL(1);
            return;

        case 0:
        case 2: /* Extremely common */
            reg = (extra >> 7) & 7;
            if ((extra & 0xfc00) == 0x5c00)
            {
                switch (extra & 0x7f)
                {
                    case 0x00:
                        fmov_pi(reg);
                        break;
                    case 0x0b:
                        fmov_log10_2(reg);
                        break;
                    case 0x0c:
                        #if USE_LONG_DOUBLE
                        fmov_ext_rm(reg, (uintptr) & const_e);
                        #else
                        fmov_rm(reg, (uintptr) & const_e);
                        #endif
                        break;
                    case 0x0d:
                        fmov_log2_e(reg);
                        break;
                    case 0x0e:
                        #if USE_LONG_DOUBLE
                        fmov_ext_rm(reg, (uintptr) & const_log10_e);
                        #else
                        fmov_rm(reg, (uintptr) & const_log10_e);
                        #endif
                        break;
                    case 0x0f:
                        fmov_0(reg);
                        break;
                    case 0x30:
                        fmov_loge_2(reg);
                        break;
                    case 0x31:
                        #if USE_LONG_DOUBLE
                        fmov_ext_rm(reg, (uintptr) & const_loge_10);
                        #else
                        fmov_rm(reg, (uintptr) & const_loge_10);
                        #endif
                        break;
                    case 0x32:
                        fmov_1(reg);
                        break;
                    case 0x33:
                    case 0x34:
                    case 0x35:
                    case 0x36:
                    case 0x37:
                    case 0x38:
                    case 0x39:
                    case 0x3a:
                    case 0x3b:
                        #if USE_LONG_DOUBLE
                    case 0x3c:
                    case 0x3d:
                    case 0x3e:
                    case 0x3f:
                        fmov_ext_rm(reg, (uintptr)(power10 + (extra & 0x7f) - 0x32));
                        #else
                        fmov_rm(reg, (uintptr)(power10 + (extra & 0x7f) - 0x32));
                        #endif
                        break;
                    default:
                        /* This is not valid, so we fail */
                        FAIL(1);
                        return;
                }
                return;
            }

            switch (extra & 0x7f)
            {
                case 0x00: /* FMOVE */
                case 0x40: /* Explicit rounding. This is just a quick fix. Same
                            * for all other cases that have three choices */
                case 0x44:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fmov_rr(reg, src);
                    MAKE_FPSR(src);
                    break;
                case 0x01: /* FINT */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                case 0x02: /* FSINH */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x03: /* FINTRZ */
                    #if USE_X86_FPUCW
                    /* If we have control over the CW, we can do this */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    mov_l_ri(S1, 16); /* Switch to "round to zero" mode */
                    fldcw_m_indexed(S1, (uae_u32)x86_fpucw);

                    frndint_rr(reg, src);

                    /* restore control word */
                    mov_l_rm(S1, (uintptr) & regs.fpcr);
                    and_l_ri(S1, 0x000000f0);
                    fldcw_m_indexed(S1, (uintptr)x86_fpucw);

                    MAKE_FPSR(reg);
                    break;
                    #endif
                    FAIL(1);
                    return;
                    break;
                case 0x04: /* FSQRT */
                case 0x41:
                case 0x45:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fsqrt_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x06: /* FLOGNP1 */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x08: /* FETOXM1 */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x09: /* FTANH */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x0a: /* FATAN */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x0c: /* FASIN */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x0d: /* FATANH */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x0e: /* FSIN */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fsin_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x0f: /* FTAN */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x10: /* FETOX */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fetox_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x11: /* FTWOTOX */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    ftwotox_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x12: /* FTENTOX */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x14: /* FLOGN */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x15: /* FLOG10 */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x16: /* FLOG2 */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    flog2_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x18: /* FABS */
                case 0x58:
                case 0x5c:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fabs_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x19: /* FCOSH */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x1a: /* FNEG */
                case 0x5a:
                case 0x5e:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fneg_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x1c: /* FACOS */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x1d: /* FCOS */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fcos_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x1e: /* FGETEXP */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x1f: /* FGETMAN */
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x20: /* FDIV */
                case 0x60:
                case 0x64:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fdiv_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x21: /* FMOD */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    frem_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x22: /* FADD */
                case 0x62:
                case 0x66:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fadd_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x23: /* FMUL */
                case 0x63:
                case 0x67:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fmul_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x24: /* FSGLDIV */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fdiv_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x25: /* FREM */
                    // gb-- disabled because the quotient byte must be computed
                    // otherwise, free rotation in ClarisWorks doesn't work.
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    frem1_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x26: /* FSCALE */
                    dont_care_fflags();
                    FAIL(1);
                    return;
                    break;
                case 0x27: /* FSGLMUL */
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fmul_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x28: /* FSUB */
                case 0x68:
                case 0x6c:
                    dont_care_fflags();
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fsub_rr(reg, src);
                    MAKE_FPSR(reg);
                    break;
                case 0x30: /* FSINCOS */
                case 0x31:
                case 0x32:
                case 0x33:
                case 0x34:
                case 0x35:
                case 0x36:
                case 0x37:
                    FAIL(1);
                    return;
                    dont_care_fflags();
                    break;
                case 0x38: /* FCMP */
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fmov_rr(FP_RESULT, reg);
                    fsub_rr(FP_RESULT, src); /* Right way? */
                    break;
                case 0x3a: /* FTST */
                    src = get_fp_value(opcode, extra);
                    if (src < 0)
                    {
                        FAIL(1); /* Illegal instruction */
                        return;
                    }
                    fmov_rr(FP_RESULT, src);
                    break;
                default:
                    FAIL(1);
                    return;
                    break;
            }
            return;
    }
    m68k_setpc(m68k_getpc() - 4);
    fpuop_illg(opcode, extra);
}